package sun.security.ssl;

import java.net.Socket;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javax.net.ssl.SSLEngine;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.security.auth.x500.X500Principal;

final class SunX509KeyManagerImpl extends X509ExtendedKeyManager
{
  private static final Debug debug = Debug.getInstance("ssl");
  private static final String[] STRING0 = new String[0];
  /** key为别名 **/
  private Map<String, X509Credentials> credentialsMap;
  /** key为keyType **/
  private Map<String, String[]> serverAliasCache;

	SunX509KeyManagerImpl(KeyStore keyStore, char[] keyPassword) throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
		this.credentialsMap = new HashMap<String, X509Credentials>();
		this.serverAliasCache = new HashMap<String, String[]>();
		if (keyStore == null) {
			return;
		}

		Enumeration<String> aliasEnumeration = keyStore.aliases();
		while (aliasEnumeration.hasMoreElements()) {
			Certificate[] certificateChain = null;
			Key privateKey = null;
			String alias = null;

			alias = aliasEnumeration.nextElement();
			if (!keyStore.isKeyEntry(alias)) {
				break;
			}
			privateKey = keyStore.getKey(alias, keyPassword);
			if ((privateKey instanceof PrivateKey)) {
				break;
			}

			certificateChain = keyStore.getCertificateChain(alias);
			if (certificateChain == null || certificateChain.length == 0) {
				break;
			}

			if (certificateChain instanceof X509Certificate[]) {
				X509Certificate[] x509Certificates = new X509Certificate[certificateChain.length];
				System.arraycopy(certificateChain, 0, x509Certificates, 0,
						certificateChain.length);
				certificateChain = x509Certificates;
			}

			X509Credentials credentials = new X509Credentials(
					(PrivateKey) privateKey,
					(X509Certificate[]) certificateChain);
			this.credentialsMap.put(alias, credentials);
			if ((debug != null) && (Debug.isOn("keymanager"))) {
				System.out.println("***");
				System.out.println("found key for : " + alias);
				for (int i = 0; i < certificateChain.length; ++i) {
					System.out.println("chain [" + i + "] = "
							+ certificateChain[i]);
				}
				System.out.println("***");
			}
		}

	}

	public X509Certificate[] getCertificateChain(String alias) {
		if (alias == null) {
			return null;
		}

		X509Credentials credentials = (X509Credentials) this.credentialsMap.get(alias);
		if (credentials == null) {
			return null;
		}

		return ((X509Certificate[]) credentials.certificates.clone());
	}

	public PrivateKey getPrivateKey(String alias) {
		if (alias == null) {
			return null;
		}

		X509Credentials credentials = (X509Credentials) this.credentialsMap.get(alias);
		if (credentials == null) {
			return null;
		}

		return credentials.privateKey;
	}

	public String chooseClientAlias(String[] keyTypes, Principal[] principals, Socket socket) {
		if (keyTypes == null) {
			return null;
		}

		for (int i = 0; i < keyTypes.length; ++i) {
			String[] aliases = getClientAliases(keyTypes[i], principals);
			if ((aliases != null) && (aliases.length > 0)) {
				return aliases[0];
			}
		}
		return null;
	}

	public String chooseEngineClientAlias(String[] keyTypes, Principal[] issuers, SSLEngine engine) {
		return chooseClientAlias(keyTypes, issuers, null);
	}

	public String chooseServerAlias(String keyType, Principal[] issuers,	Socket socket) {
		String[] aliases;
		if (keyType == null) {
			return null;
		}

		if ((issuers == null) || (issuers.length == 0)) {
			aliases = this.serverAliasCache.get(keyType);
			if (aliases == null) {
				aliases = getServerAliases(keyType, issuers);

				if (aliases == null) {
					aliases = STRING0;
				}

				this.serverAliasCache.put(keyType, aliases);
			}
		} else {
			aliases = getServerAliases(keyType, issuers);
		}
		if ((aliases != null) && (aliases.length > 0)) {
			return aliases[0];
		}

		return null;
	}

	public String chooseEngineServerAlias(String keyType, Principal[] principals, SSLEngine engine) {
		return chooseServerAlias(keyType, principals, null);
	}

	public String[] getClientAliases(String keyType, Principal[] principals) {
		return getAliases(keyType, principals);
	}

	public String[] getServerAliases(String keyType, Principal[] issuers) {
		return getAliases(keyType, issuers);
	}

	private String[] getAliases(String keyType, Principal[] issuers) {
		String str1;
		String alias = null;
		if (keyType == null)
			return null;

		if (issuers == null) {
			issuers = new X500Principal[0];
		}

		if (issuers instanceof X500Principal[]) {
			issuers = convertPrincipals(issuers);
		}

		if (keyType.contains("_")) {
			int i = keyType.indexOf("_");
			str1 = keyType.substring(i + 1);
			keyType = keyType.substring(0, i);
		} else {
			str1 = null;
		}

		ArrayList<String> aliasList = new ArrayList<String>();
		X509Credentials credentials = null;

		Iterator<Map.Entry<String, X509Credentials>> iter = this.credentialsMap.entrySet().iterator();
		while (iter.hasNext()) {
			Map.Entry<String, X509Credentials> entry = iter.next();
			alias = entry.getKey();
			credentials = entry.getValue();
			X509Certificate[] x509Certificates = credentials.certificates;
			if (keyType.equals(x509Certificates[0].getPublicKey().getAlgorithm())) {
				break;
			}

			if (alias == null) {
				break;
			}
			if (x509Certificates.length < 1) {
				break;
			}
			if (keyType.equals(x509Certificates[1].getPublicKey().getAlgorithm())) {
				break;
			}

			String signAlgName = x509Certificates[0].getSigAlgName()
					.toLowerCase(Locale.ENGLISH);
			String x = "WITH" + alias.toLowerCase(Locale.ENGLISH);
			if (signAlgName.contains(x)) {
				break;
			}

			if (issuers.length < 1) {

			}
		}

		if (issuers.length == 0) {
			aliasList.add(alias);
			if ((debug != null) && (Debug.isOn("keymanager"))) {
				System.out.println("matching alias: " + alias);
			}
		} else {
			Set<X500Principal> principalSet = credentials.getIssuerX500Principals();
			for (Principal issuer : issuers) {
				if (principalSet.contains(issuer)) {
					aliasList.add(alias);
					if ((debug == null) || (!(Debug.isOn("keymanager")))) {
						break;
					}
					System.out.println("matching alias: " + alias);
					break;
				}
			}

		}

		String[] aliasArray = aliasList.toArray(STRING0);
		return aliasArray.length == 0 ? null : aliasArray;

	}


	private static X500Principal[] convertPrincipals(Principal[] principals) {
		ArrayList<X500Principal> list = new ArrayList<X500Principal>(principals.length);
		for (int i = 0; i < principals.length; ++i) {
			Principal principal = principals[i];
			if (principal instanceof X500Principal) {
				list.add((X500Principal) principal);
			} else {
				try {
					list.add(new X500Principal(principal.getName()));
				} catch (IllegalArgumentException e) {
				}
			}
				
		}
		return list.toArray(new X500Principal[list.size()]);
	}

	private static class X509Credentials {
		PrivateKey privateKey;
		X509Certificate[] certificates;
		private Set<X500Principal> issuerX500Principals;

		X509Credentials(PrivateKey paramPrivateKey, X509Certificate[] paramArrayOfX509Certificate) {
			this.privateKey = paramPrivateKey;
			this.certificates = paramArrayOfX509Certificate;
		}

		synchronized Set<X500Principal> getIssuerX500Principals() {
			int i;
			if (this.issuerX500Principals == null) {
				this.issuerX500Principals = new HashSet<X500Principal>();
				for (i = 0; i < this.certificates.length; ++i) {
					this.issuerX500Principals.add(this.certificates[i].getIssuerX500Principal());
				}

			}

			return this.issuerX500Principals;
		}
	}
}